#include "FileReadBehaviorWAV.h"
#include <qcoreapplication.h>

#define NON_BWF_DATA_OFFSET 40
#define BWF_FILESIZE_OFFSET 28 
#define OFFSET_TO_FMT_CHK_OFFSET_VALUE 16
#define META_CHK_SIZE 920
#define DESC_CHK_SIZE 256
#define DESC_CHK_OFFSET 20
#define BYTE_SIZE 8

using namespace std;

FileReadBehaviorWAV::FileReadBehaviorWAV() : IFileReadBehavior()
{
}

int FileReadBehaviorWAV::calculateWaveform ( SoundSample* parentSample, int width )
{
	
	// set the progress bar back to zero
	emit percentFinished(0);

    const int SOUNDMINER_OFFSET = 854;

    // need to convert to UTF-8 in case of illegal characters in path or filename
    ifstream audioFile;
    audioFile.open(parentSample->getFilepath().toUtf8());
	
    if (!audioFile) {
        cerr << "Unable to open file";
        exit(1);   // call system to stop
    }

	char* data = NULL;
	long fileSize = 0;
	int audioDataOffset = 0;

	// int pointer that will be used to move around in the raw data
	int* intPtr = NULL;

	// get filesize
	audioFile.seekg(0, ios::end);
	fileSize = audioFile.tellg();
	
	// set the filesize
	parentSample->setFilesize(fileSize);

	// set get pointer back to zero
	audioFile.seekg (0, ios::beg);

	// allocate space for the whole file
	data = new char[fileSize];

	// fill the buffer and close the stream
	audioFile.read(data, fileSize);
	audioFile.close();
	
	int* bytesToReadPtr;
	int bytesToRead;
	// check if the wave file is a BWF
	char formatCheck[5];
	strncpy(formatCheck, (data+12), 4);
	formatCheck[4] = '\0';
	
	// in case of a BWAV file
	// strcmp returns zero if both strings are equal
	if( !strcmp(formatCheck, "bext"))
	{
		// read the fmt-chunk offset
		// it's 858 if it's a non-Soundminer audio file, else it's 854
		intPtr = (int*)(data+16);

		if ( *intPtr == SOUNDMINER_OFFSET )
		{
			audioDataOffset = 902;
		}
		else
		{
			audioDataOffset = 906;
		}

		// get bytes to read
		bytesToReadPtr = (int*) (data + audioDataOffset);
		bytesToRead = *bytesToReadPtr;

		audioDataOffset += 4;
	}
	else 
	{
		// handle file without broadcast wave extension
		// get bytes to read
		audioDataOffset = 44;
		bytesToReadPtr = (int*) (data + 40);
		bytesToRead = *bytesToReadPtr;
		
	}

	// if our widget is i.e. 800 pixels wide, we read 800 datasets in the file
	// we just look at one channel if it's a stereo file
	// but we need to determine which kind of file it actually is
	int span = ((bytesToRead) / (parentSample->getChannels().toInt() * (parentSample->getBitdepth().toInt() >> 3)) / width);

	// points to one sample, we need to make it int because it could be 24 bit per channel
	int* samplePtr = NULL;

	// give the pointer the address of the first audio sample
	char* charPtr = data + audioDataOffset;
	
	// the value that we need to shift the pointer around, 
	// calculated from the number of channels and the bitdepth
	int sampleJumpDistance = (parentSample->getChannels().toInt() * (parentSample->getBitdepth().toInt() >> 3));
	int sampleMask, signMask, fillMask;
	
	// both the file format and the processor works with Little Endian
	// This means that 0x12345678 would be 78 56 34 12 in memory
	if (parentSample->getBitdepth().toInt() == 8) {
		
		sampleMask = 0xff;
		
		// we have to check wether it's a negative number or not
		// 1000 0000
		signMask = 128;
		
		// mask to fill up with 1s in case of a negative number
		fillMask = 0xffffff00;
	}
	else if ( parentSample->getBitdepth().toInt() == 16)
	{
		sampleMask = 0xffff;
		
		// we have to check wether it's a negative number or not
		// 1000 0000 0000 0000
		signMask = 32768;
		
		// mask to fill up with 1s in case of a negative number
		fillMask = 0xffff0000;
	}
	else if ( parentSample->getBitdepth().toInt() == 24)
	{
		sampleMask = 0xffffff;
		
		// we have to check wether it's a negative number or not
		// 1000 0000 0000 0000 0000 0000
		signMask =  8388608;
		
		// mask to fill up with 1s in case of a negative number
		fillMask = 0xff000000;
	}

	cout << "Bytes to read : " << fileSize << "  DataOffset : " << audioDataOffset << "  Span : " << span << "  Width : " << width;

	// allocate space for the plot data buffers
	int* plotDataUp   = new int[width];
	int* plotDataDown = new int[width];

	// compute the minimum or maximum of a "span" of samples
	for ( int i=0; i < width; i++ )
	{
		int minimum = 0 , maximum = 0;
		int tempInt = 0, signInt = 0;

		emit percentFinished ( int (((i+1) * 100) / width));
				
		for ( int j = 0; j < span; j++ )
		{
			samplePtr = (int*) charPtr;
			
			// we don't want to modify memory!!!
			tempInt = *samplePtr & sampleMask;
			
			// if the first bit of the resulting value after applying the mask
			// is 1, the value is negative, so we need to fill the left portion of our int with 1
			signInt = tempInt & signMask;
			
			if( signInt > 0)
			{
				// fill the left portion with 1s to retain negative numbers
				tempInt |= fillMask;
			}

			if ( tempInt > maximum )
			{
				maximum = tempInt;
			}
			else if ( tempInt < minimum)
			{
				minimum = tempInt;
			}

			charPtr +=  sampleJumpDistance;	// shift to next sample
		}

		plotDataUp[i]	= maximum;
		plotDataDown[i] = minimum;
	}

	cout << "plotDataUp und plotDataDown eingelesen" << endl;

	delete[] data;
	
	parentSample->setPlotDataUp(plotDataUp);
	parentSample->setPlotDataDown(plotDataDown);

	return 0;
}

int FileReadBehaviorWAV :: gatherMetaInformation( SoundSample* parentSample )
{
	// need to convert to UTF-8 in case of illegal characters in path or filename
	ifstream audioFile;
	audioFile.open(parentSample->getFilepath().toUtf8());
	
	long fileSize = 0;
	int audioDataOffset = 0;
	
	// int pointer that will be used to move around in the raw data
	int* intPtr = NULL;
	
	// get filesize
	audioFile.seekg(0, ios::end);
	fileSize = audioFile.tellg();
	
	// set the filesize
	parentSample->setFilesize(fileSize);
	
	// set get pointer back to zero
	audioFile.seekg (0, ios::beg);
		
	if ( audioFile )
	{
		// char Array to save the first 920 bytes of the file
		uint size = META_CHK_SIZE;
		char data[size];
	
		audioFile.getline(data, size);
		
		// check if the wave file is a BWF
		char formatCheck[5];
		
		strncpy(formatCheck, (data+12), 4);
		formatCheck[4] = '\0';
		
		// in case of a BWAV file
		if( !strcmp(formatCheck, "bext"))
		{
			// stringstream to convert from number to string
			stringstream strstream;
			
			// point to the general int-value telling us the "fmt-chunk" offset
			// the value is not correct yet, we need to increase by 20 to get 
			// the offset from the beginning of the file to the beginning 
			// of the format chunk.

			intPtr = (int*)(data + OFFSET_TO_FMT_CHK_OFFSET_VALUE);
			audioDataOffset = *intPtr + 20;
			
			// ===============================================================
			// S E T     D E S C R I P T I O N
			// point to the file description
			char description[DESC_CHK_SIZE];
			
			// copy the description into char-array
			// using memcpy because strcpy stops on the first binary 0
			memcpy (description, (data + DESC_CHK_OFFSET), DESC_CHK_SIZE);
			
			// set the description
			parentSample->setDescription(description);
			
			// ===============================================================
			// S E T     N U M B E R   O F    C H A N N E L S
			// set the offset to the number of channels
			short* shortPtr = (short*)(data + audioDataOffset + 8 + 2);
			strstream << *shortPtr;
			
			char ch_temp[4];
			
			strcpy(ch_temp, strstream.str().c_str());
			
			parentSample->setChannels(ch_temp);
			
			// ===============================================================
			// S E T    S A M P L E R A T E
			// clear the stringstream for further use
			strstream.str("");
			strstream.clear();
			
			// set the offset to the Samplerate
			intPtr = (int*)(data + audioDataOffset + 8 + 2 + 2);
			strstream << *intPtr;
			
			char sr_temp[10];
			
			strcpy(sr_temp, strstream.str().c_str());
			
			parentSample->setSamplerate(sr_temp);
			
			// ===============================================================
			// S E T   B I T D E P T H
			// clear the stringstream for further use
			strstream.str("");
			strstream.clear();
			
			// set the offset to the number of Bits per sample
			shortPtr = (short*)(data + audioDataOffset + 8 + 2 + 2 + 4 + 4 + 2);
			strstream << *shortPtr;
			
			char bit_temp[4];
			strcpy(bit_temp, strstream.str().c_str());
			
			parentSample->setBitdepth(bit_temp);
			
			// ===============================================================
			// S E T   F I L E L E N G T H
			// set the int pointer position to read the data chunk length
			// ( that's right after the 'data' in the file )
			// set the length in seconds
			intPtr = (int*)(data + audioDataOffset + BWF_FILESIZE_OFFSET);
			
			parentSample->setLength( int(*intPtr / 
					((parentSample->getBitdepth().toInt()/ BYTE_SIZE) * 
					 parentSample->getChannels().toInt() * 
					 parentSample->getSamplerate().toInt())));
			
			
		} // BWF file handling end
		
		// non-BWF-file-handling
		else 
		{
			// stringstream to convert from number to string
			stringstream strstream;
			
			// ===============================================================
			// S E T     D E S C R I P T I O N
			// no description in non-BWF, so set ""
			parentSample->setDescription("");
			
			// ===============================================================
			// S E T     N U M B E R   O F    C H A N N E L S
			// set the offset to the number of channels
			short* shortPtr = (short*)(data + 22);
			strstream << *shortPtr;
			
			char ch_temp[4];
			strcpy(ch_temp, strstream.str().c_str());
			
			parentSample->setChannels(ch_temp);
			
			// ===============================================================
			// S E T    S A M P L E R A T E
			// clear the stringstream for further use
			strstream.str("");
			strstream.clear();
			
			// set the offset to the Samplerate
			intPtr = (int*)(data + 24);
			strstream << *intPtr;
			
			char sr_temp[8];
			strcpy(sr_temp, strstream.str().c_str());
			
			parentSample->setSamplerate(sr_temp);
			
			// ===============================================================
			// S E T   B I T D E P T H
			// clear the stringstream for further use
			strstream.str("");
			strstream.clear();
			
			// set the offset to the number of Bits per sample
			shortPtr = (short*)(data + 24 + 4 + 2 + 4);
			strstream << *shortPtr;
			
			char bit_temp[4];
			strcpy(bit_temp, strstream.str().c_str());
			
			parentSample->setBitdepth(bit_temp);
			
			// ===============================================================
			// S E T   F I L E L E N G T H
			// set the int pointer position to read the data chunk length
			// ( that's right after the 'data' in the file )
			// set the length in seconds
			
			parentSample->setLength( int((fileSize - NON_BWF_DATA_OFFSET) / 
										 ((parentSample->getBitdepth().toInt()/ BYTE_SIZE) * 
										  parentSample->getChannels().toInt() * 
										  parentSample->getSamplerate().toInt())));
			
		}
		
		// close the filestream
		audioFile.close();
			
	}
	else
	{
		cout << "File could not be opened : " << parentSample->getFilepath().toStdString() << endl;
		return -1;
	}
		
	return 0;
}

